goldendict-ng/programs.cc
shenleban tongying 540dda26ed cleanup: Replace all usages of qrcx://localhost with qrc://
The replacement command:
git grep -l 'qrcx://localhost' | xargs sed -i 's/qrcx:\/\/localhost/qrc:\/\//g'

The qrcx:// URL scheme was introduced in 2009 or earlier - it is present
in the first commit in GoldenDict's git history. Back then GoldenDict
supported Qt versions earlier than 4.6, in which
QWebSecurityOrigin::addLocalScheme() was introduced. Adding the qrc URL
scheme as local obsoletes the qrcx URL scheme. GoldenDict does not
compile against Qt versions earlier than 4.6, so there is no reason to
use this custom URL scheme anymore.

Co-authored-by:  Igor Kushnir <igorkuo@gmail.com>
2023-03-05 15:20:05 -05:00

384 lines
10 KiB
C++

/* This file is (c) 2008-2012 Konstantin Isakov <ikm@goldendict.org>
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
#include "programs.hh"
#include "audiolink.hh"
#include "htmlescape.hh"
#include "utf8.hh"
#include "wstring_qt.hh"
#include "parsecmdline.hh"
#include "iconv.hh"
#include "utils.hh"
#include <QDir>
#include <QFileInfo>
namespace Programs {
using namespace Dictionary;
namespace {
class ProgramsDictionary: public Dictionary::Class
{
Config::Program prg;
public:
ProgramsDictionary( Config::Program const & prg_ ):
Dictionary::Class( prg_.id.toStdString(), vector< string >() ),
prg( prg_ )
{
}
string getName() noexcept override
{ return prg.name.toUtf8().data(); }
map< Property, string > getProperties() noexcept override
{ return map< Property, string >(); }
unsigned long getArticleCount() noexcept override
{ return 0; }
unsigned long getWordCount() noexcept override
{ return 0; }
sptr< WordSearchRequest > prefixMatch( wstring const & word,
unsigned long maxResults ) override
;
sptr< DataRequest > getArticle( wstring const &,
vector< wstring > const & alts,
wstring const &, bool ) override
;
protected:
void loadIcon() noexcept override;
};
sptr< WordSearchRequest > ProgramsDictionary::prefixMatch( wstring const & word,
unsigned long /*maxResults*/ )
{
if ( prg.type == Config::Program::PrefixMatch )
return std::make_shared<ProgramWordSearchRequest>( gd::toQString( word ), prg );
else
{
sptr< WordSearchRequestInstant > sr = std::make_shared<WordSearchRequestInstant>();
sr->setUncertain( true );
return sr;
}
}
sptr< Dictionary::DataRequest > ProgramsDictionary::getArticle(
wstring const & word, vector< wstring > const &, wstring const &, bool )
{
switch( prg.type )
{
case Config::Program::Audio:
{
// Audio results are instantaneous
string result;
string wordUtf8( Utf8::encode( word ) );
result += "<table class=\"programs_play\"><tr>";
QUrl url;
url.setScheme( "gdprg" );
url.setHost( QString::fromUtf8( getId().c_str() ) );
url.setPath( Utils::Url::ensureLeadingSlash( QString::fromUtf8( wordUtf8.c_str() ) ) );
string ref = string( "\"" ) + url.toEncoded().data() + "\"";
result += addAudioLink( ref, getId() );
result += "<td><a href=" + ref + R"(><img src="qrc:///icons/playsound.png" border="0" alt="Play"/></a></td>)";
result += "<td><a href=" + ref + ">" +
Html::escape( wordUtf8 ) + "</a></td>";
result += "</tr></table>";
sptr< DataRequestInstant > ret = std::make_shared<DataRequestInstant>( true );
ret->getData().resize( result.size() );
memcpy( &(ret->getData().front()), result.data(), result.size() );
return ret;
}
case Config::Program::Html:
case Config::Program::PlainText:
return std::make_shared<ProgramDataRequest>( gd::toQString( word ), prg );
default:
return std::make_shared<DataRequestInstant>( false );
}
}
void ProgramsDictionary::loadIcon() noexcept
{
if ( dictionaryIconLoaded )
return;
if( !prg.iconFilename.isEmpty() )
{
QFileInfo fInfo( QDir( Config::getConfigDir() ), prg.iconFilename );
if( fInfo.isFile() )
loadIconFromFile( fInfo.absoluteFilePath(), true );
}
if( dictionaryIcon.isNull() )
dictionaryIcon = dictionaryNativeIcon = QIcon(":/icons/programs.svg");
dictionaryIconLoaded = true;
}
}
RunInstance::RunInstance(): process( this )
{
connect( this, &RunInstance::processFinished, this, &RunInstance::handleProcessFinished, Qt::QueuedConnection );
connect( &process, &QProcess::finished, this, &RunInstance::processFinished );
connect( &process, &QProcess::errorOccurred, this, &RunInstance::processFinished );
}
bool RunInstance::start( Config::Program const & prg, QString const & word,
QString & error )
{
QStringList args = parseCommandLine( prg.commandLine );
if ( !args.empty() )
{
QString programName = args.first();
args.pop_front();
bool writeToStdInput = true;
for( int x = 0; x < args.size(); ++x )
if( args[ x ].indexOf( "%GDWORD%" ) >= 0 )
{
writeToStdInput = false;
args[ x ].replace( "%GDWORD%", word );
}
process.start( programName, args );
if( writeToStdInput )
{
process.write( word.toLocal8Bit() );
process.closeWriteChannel();
}
return true;
}
else
{
error = tr( "No program name was given." );
return false;
}
}
void RunInstance::handleProcessFinished()
{
// It seems that sometimes the process isn't finished yet despite being
// signalled as such. So we wait for it here, which should hopefully be
// nearly instant.
process.waitForFinished();
QByteArray output = process.readAllStandardOutput();
QString error;
if ( process.exitStatus() != QProcess::NormalExit )
error = tr( "The program has crashed." );
else
if ( int code = process.exitCode() )
error = tr( "The program has returned exit code %1." ).arg( code );
if ( !error.isEmpty() )
{
QByteArray err = process.readAllStandardError();
if ( !err.isEmpty() )
error += "\n\n" + QString::fromLocal8Bit( err );
}
emit finished( output, error );
}
ProgramDataRequest::ProgramDataRequest( QString const & word,
Config::Program const & prg_ ):
prg( prg_ )
{
connect( &instance, &RunInstance::finished, this, &ProgramDataRequest::instanceFinished );
QString error;
if ( !instance.start( prg, word, error ) )
{
setErrorString( error );
finish();
}
}
void ProgramDataRequest::instanceFinished( QByteArray output, QString error )
{
QString prog_output;
if ( !isFinished() )
{
if ( !output.isEmpty() )
{
string result = "<div class='programs_";
switch( prg.type )
{
case Config::Program::PlainText:
result += "plaintext'>";
try
{
// Check BOM if present
unsigned char * uchars = reinterpret_cast< unsigned char * >( output.data() );
if( output.length() >= 2 && uchars[ 0 ] == 0xFF && uchars[ 1 ] == 0xFE )
{
int size = output.length() - 2;
if( size & 1 )
size -= 1;
string res= Iconv::toUtf8( "UTF-16LE", output.data() + 2, size );
prog_output = QString::fromUtf8( res.c_str(), res.size() );
}
else
if( output.length() >= 2 && uchars[ 0 ] == 0xFE && uchars[ 1 ] == 0xFF )
{
int size = output.length() - 2;
if( size & 1 )
size -= 1;
string res = Iconv::toUtf8( "UTF-16BE", output.data() + 2, size );
prog_output = QString::fromUtf8( res.c_str(), res.size() );
}
else
if( output.length() >= 3 && uchars[ 0 ] == 0xEF && uchars[ 1 ] == 0xBB && uchars[ 2 ] == 0xBF )
{
prog_output = QString::fromUtf8( output.data() + 3, output.length() - 3 );
}
else
{
// No BOM, assume local 8-bit encoding
prog_output = QString::fromLocal8Bit( output );
}
}
catch( std::exception & e )
{
error = e.what();
}
result += Html::preformat( prog_output.toUtf8().data() );
break;
default:
result += "html'>";
try
{
// Check BOM if present
unsigned char * uchars = reinterpret_cast< unsigned char * >( output.data() );
if( output.length() >= 2 && uchars[ 0 ] == 0xFF && uchars[ 1 ] == 0xFE )
{
int size = output.length() - 2;
if( size & 1 )
size -= 1;
result += Iconv::toUtf8( "UTF-16LE", output.data() + 2, size );
}
else
if( output.length() >= 2 && uchars[ 0 ] == 0xFE && uchars[ 1 ] == 0xFF )
{
int size = output.length() - 2;
if( size & 1 )
size -= 1;
result += Iconv::toUtf8( "UTF-16BE", output.data() + 2, size );
}
else
if( output.length() >= 3 && uchars[ 0 ] == 0xEF && uchars[ 1 ] == 0xBB && uchars[ 2 ] == 0xBF )
{
result += output.data() + 3;
}
else
{
// We assume html data is in utf8 encoding already.
result += output.data();
}
}
catch( std::exception & e )
{
error = e.what();
}
}
result += "</div>";
Mutex::Lock _( dataMutex );
data.resize( result.size() );
memcpy( data.data(), result.data(), data.size() );
hasAnyData = true;
}
if ( !error.isEmpty() )
setErrorString( error );
finish();
}
}
void ProgramDataRequest::cancel()
{
finish();
}
ProgramWordSearchRequest::ProgramWordSearchRequest( QString const & word,
Config::Program const & prg_ ):
prg( prg_ )
{
connect( &instance, &RunInstance::finished, this, &ProgramWordSearchRequest::instanceFinished );
QString error;
if ( !instance.start( prg, word, error ) )
{
setErrorString( error );
finish();
}
}
void ProgramWordSearchRequest::instanceFinished( QByteArray output, QString error )
{
if ( !isFinished() )
{
// Handle any Windows artifacts
output.replace( "\r\n", "\n" );
QStringList result =
QString::fromUtf8( output ).split( "\n", Qt::SkipEmptyParts );
for( int x = 0; x < result.size(); ++x )
matches.push_back( Dictionary::WordMatch( gd::toWString( result[ x ] ) ) );
if ( !error.isEmpty() )
setErrorString( error );
finish();
}
}
void ProgramWordSearchRequest::cancel()
{
finish();
}
vector< sptr< Dictionary::Class > > makeDictionaries(
Config::Programs const & programs )
{
vector< sptr< Dictionary::Class > > result;
for( Config::Programs::const_iterator i = programs.begin();
i != programs.end(); ++i )
if ( i->enabled )
result.push_back( std::make_shared<ProgramsDictionary>( *i ) );
return result;
}
}