summaryrefslogtreecommitdiffstats
path: root/khelpcenter/searchengine.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'khelpcenter/searchengine.cpp')
-rw-r--r--khelpcenter/searchengine.cpp502
1 files changed, 502 insertions, 0 deletions
diff --git a/khelpcenter/searchengine.cpp b/khelpcenter/searchengine.cpp
new file mode 100644
index 000000000..2156ee244
--- /dev/null
+++ b/khelpcenter/searchengine.cpp
@@ -0,0 +1,502 @@
+#include "stdlib.h"
+
+#include <qstylesheet.h>
+#include <kapplication.h>
+#include <kconfig.h>
+#include <kdebug.h>
+#include <kstandarddirs.h>
+#include <kprocess.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+
+#include "docmetainfo.h"
+#include "formatter.h"
+#include "view.h"
+#include "searchhandler.h"
+#include "prefs.h"
+
+#include "searchengine.h"
+
+namespace KHC
+{
+
+SearchTraverser::SearchTraverser( SearchEngine *engine, int level ) :
+ mMaxLevel( 999 ), mEngine( engine), mLevel( level )
+{
+#if 0
+ kdDebug() << "SearchTraverser(): " << mLevel
+ << " 0x" << QString::number( int( this ), 16 ) << endl;
+#endif
+}
+
+SearchTraverser::~SearchTraverser()
+{
+#if 0
+ kdDebug() << "~SearchTraverser(): " << mLevel
+ << " 0x" << QString::number( int( this ), 16 ) << endl;
+#endif
+
+ QString section;
+ if ( parentEntry() ) {
+ section = parentEntry()->name();
+ } else {
+ section = ("Unknown Section");
+ }
+
+ if ( !mResult.isEmpty() ) {
+ mEngine->view()->writeSearchResult(
+ mEngine->formatter()->sectionHeader( section ) );
+ mEngine->view()->writeSearchResult( mResult );
+ }
+}
+
+void SearchTraverser::process( DocEntry * )
+{
+ kdDebug() << "SearchTraverser::process()" << endl;
+}
+
+void SearchTraverser::startProcess( DocEntry *entry )
+{
+// kdDebug() << "SearchTraverser::startProcess(): " << entry->name() << " "
+// << "SEARCH: '" << entry->search() << "'" << endl;
+
+ if ( !mEngine->canSearch( entry ) || !entry->searchEnabled() ) {
+ mNotifyee->endProcess( entry, this );
+ return;
+ }
+
+// kdDebug() << "SearchTraverser::startProcess(): " << entry->identifier()
+// << endl;
+
+ SearchHandler *handler = mEngine->handler( entry->documentType() );
+
+ if ( !handler ) {
+ QString txt;
+ if ( entry->documentType().isEmpty() ) {
+ txt = i18n("Error: No document type specified.");
+ } else {
+ txt = i18n("Error: No search handler for document type '%1'.")
+ .arg( entry->documentType() );
+ }
+ showSearchError( handler, entry, txt );
+ return;
+ }
+
+ connectHandler( handler );
+
+ handler->search( entry, mEngine->words(), mEngine->maxResults(),
+ mEngine->operation() );
+
+// kdDebug() << "SearchTraverser::startProcess() done: " << entry->name() << endl;
+}
+
+void SearchTraverser::connectHandler( SearchHandler *handler )
+{
+ QMap<SearchHandler *,int>::Iterator it;
+ it = mConnectCount.find( handler );
+ int count = 0;
+ if ( it != mConnectCount.end() ) count = *it;
+ if ( count == 0 ) {
+ connect( handler, SIGNAL( searchError( SearchHandler *, DocEntry *, const QString & ) ),
+ SLOT( showSearchError( SearchHandler *, DocEntry *, const QString & ) ) );
+ connect( handler, SIGNAL( searchFinished( SearchHandler *, DocEntry *, const QString & ) ),
+ SLOT( showSearchResult( SearchHandler *, DocEntry *, const QString & ) ) );
+ }
+ mConnectCount[ handler ] = ++count;
+}
+
+void SearchTraverser::disconnectHandler( SearchHandler *handler )
+{
+ QMap<SearchHandler *,int>::Iterator it;
+ it = mConnectCount.find( handler );
+ if ( it == mConnectCount.end() ) {
+ kdError() << "SearchTraverser::disconnectHandler() handler not connected."
+ << endl;
+ } else {
+ int count = *it;
+ --count;
+ if ( count == 0 ) {
+ disconnect( handler, SIGNAL( searchError( SearchHandler *, DocEntry *, const QString & ) ),
+ this, SLOT( showSearchError( SearchHandler *, DocEntry *, const QString & ) ) );
+ disconnect( handler, SIGNAL( searchFinished( SearchHandler *, DocEntry *, const QString & ) ),
+ this, SLOT( showSearchResult( SearchHandler *, DocEntry *, const QString & ) ) );
+ }
+ mConnectCount[ handler ] = count;
+ }
+}
+
+DocEntryTraverser *SearchTraverser::createChild( DocEntry *parentEntry )
+{
+// kdDebug() << "SearchTraverser::createChild() level " << mLevel << endl;
+
+ if ( mLevel >= mMaxLevel ) {
+ ++mLevel;
+ return this;
+ } else {
+ DocEntryTraverser *t = new SearchTraverser( mEngine, mLevel + 1 );
+ t->setParentEntry( parentEntry );
+ return t;
+ }
+}
+
+DocEntryTraverser *SearchTraverser::parentTraverser()
+{
+// kdDebug() << "SearchTraverser::parentTraverser(): level: " << mLevel << endl;
+
+ if ( mLevel > mMaxLevel ) {
+ return this;
+ } else {
+ return mParent;
+ }
+}
+
+void SearchTraverser::deleteTraverser()
+{
+// kdDebug() << "SearchTraverser::deleteTraverser()" << endl;
+
+ if ( mLevel > mMaxLevel ) {
+ --mLevel;
+ } else {
+ delete this;
+ }
+}
+
+void SearchTraverser::showSearchError( SearchHandler *handler, DocEntry *entry, const QString &error )
+{
+// kdDebug() << "SearchTraverser::showSearchError(): " << entry->name()
+// << endl;
+
+ mResult += mEngine->formatter()->docTitle( entry->name() );
+ mResult += mEngine->formatter()->paragraph( error );
+
+ mEngine->logError( entry, error );
+
+ disconnectHandler( handler );
+
+ mNotifyee->endProcess( entry, this );
+}
+
+void SearchTraverser::showSearchResult( SearchHandler *handler, DocEntry *entry, const QString &result )
+{
+// kdDebug() << "SearchTraverser::showSearchResult(): " << entry->name()
+// << endl;
+
+ mResult += mEngine->formatter()->docTitle( entry->name() );
+ mResult += mEngine->formatter()->processResult( result );
+
+ disconnectHandler( handler );
+
+ mNotifyee->endProcess( entry, this );
+}
+
+void SearchTraverser::finishTraversal()
+{
+// kdDebug() << "SearchTraverser::finishTraversal()" << endl;
+
+ mEngine->view()->writeSearchResult( mEngine->formatter()->footer() );
+ mEngine->view()->endSearchResult();
+
+ mEngine->finishSearch();
+}
+
+
+SearchEngine::SearchEngine( View *destination )
+ : QObject(),
+ mProc( 0 ), mSearchRunning( false ), mView( destination ),
+ mRootTraverser( 0 )
+{
+ mLang = KGlobal::locale()->language().left( 2 );
+}
+
+SearchEngine::~SearchEngine()
+{
+ delete mRootTraverser;
+}
+
+bool SearchEngine::initSearchHandlers()
+{
+ QStringList resources = KGlobal::dirs()->findAllResources(
+ "appdata", "searchhandlers/*.desktop" );
+ QStringList::ConstIterator it;
+ for( it = resources.begin(); it != resources.end(); ++it ) {
+ QString filename = *it;
+ kdDebug() << "SearchEngine::initSearchHandlers(): " << filename << endl;
+ SearchHandler *handler = SearchHandler::initFromFile( filename );
+ if ( !handler || !handler->checkPaths() ) {
+ QString txt = i18n("Unable to initialize SearchHandler from file '%1'.")
+ .arg( filename );
+ kdWarning() << txt << endl;
+// KMessageBox::sorry( mView->widget(), txt );
+ } else {
+ QStringList documentTypes = handler->documentTypes();
+ QStringList::ConstIterator it;
+ for( it = documentTypes.begin(); it != documentTypes.end(); ++it ) {
+ mHandlers.insert( *it, handler );
+ }
+ }
+ }
+
+ if ( mHandlers.isEmpty() ) {
+ QString txt = i18n("No valid search handler found.");
+ kdWarning() << txt << endl;
+// KMessageBox::sorry( mView->widget(), txt );
+ return false;
+ }
+
+ return true;
+}
+
+void SearchEngine::searchStdout(KProcess *, char *buffer, int len)
+{
+ if ( !buffer || len == 0 )
+ return;
+
+ QString bufferStr;
+ char *p;
+ p = (char*) malloc( sizeof(char) * (len+1) );
+ p = strncpy( p, buffer, len );
+ p[len] = '\0';
+
+ mSearchResult += bufferStr.fromUtf8(p);
+
+ free(p);
+}
+
+void SearchEngine::searchStderr(KProcess *, char *buffer, int len)
+{
+ if ( !buffer || len == 0 )
+ return;
+
+ mStderr.append( QString::fromUtf8( buffer, len ) );
+}
+
+void SearchEngine::searchExited(KProcess *)
+{
+ kdDebug() << "Search terminated" << endl;
+ mSearchRunning = false;
+}
+
+bool SearchEngine::search( QString words, QString method, int matches,
+ QString scope )
+{
+ if ( mSearchRunning ) return false;
+
+ // These should be removed
+ mWords = words;
+ mMethod = method;
+ mMatches = matches;
+ mScope = scope;
+
+ // Saner variables to store search parameters:
+ mWordList = QStringList::split( " ", words );
+ mMaxResults = matches;
+ if ( method == "or" ) mOperation = Or;
+ else mOperation = And;
+
+ KConfig *cfg = KGlobal::config();
+ cfg->setGroup( "Search" );
+ QString commonSearchProgram = cfg->readPathEntry( "CommonProgram" );
+ bool useCommon = cfg->readBoolEntry( "UseCommonProgram", false );
+
+ if ( commonSearchProgram.isEmpty() || !useCommon ) {
+ if ( !mView ) {
+ return false;
+ }
+
+ QString txt = i18n("Search Results for '%1':").arg( QStyleSheet::escape(words) );
+
+ mStderr = "<b>" + txt + "</b>\n";
+
+ mView->beginSearchResult();
+ mView->writeSearchResult( formatter()->header( i18n("Search Results") ) );
+ mView->writeSearchResult( formatter()->title( txt ) );
+
+ if ( mRootTraverser ) {
+ kdDebug() << "SearchEngine::search(): mRootTraverser not null." << endl;
+ return false;
+ }
+ mRootTraverser = new SearchTraverser( this, 0 );
+ DocMetaInfo::self()->startTraverseEntries( mRootTraverser );
+
+ return true;
+ } else {
+ QString lang = KGlobal::locale()->language().left(2);
+
+ if ( lang.lower() == "c" || lang.lower() == "posix" )
+ lang = "en";
+
+ // if the string contains '&' replace with a '+' and set search method to and
+ if (mWords.find("&") != -1) {
+ mWords.replace("&", " ");
+ method = "and";
+ }
+
+ // replace whitespace with a '+'
+ mWords = mWords.stripWhiteSpace();
+ mWords = mWords.simplifyWhiteSpace();
+ mWords.replace(QRegExp("\\s"), "+");
+
+ commonSearchProgram = substituteSearchQuery( commonSearchProgram );
+
+ kdDebug() << "Common Search: " << commonSearchProgram << endl;
+
+ mProc = new KProcess();
+
+ QStringList cmd = QStringList::split( " ", commonSearchProgram );
+ QStringList::ConstIterator it;
+ for( it = cmd.begin(); it != cmd.end(); ++it ) {
+ QString arg = *it;
+ if ( arg.left( 1 ) == "\"" && arg.right( 1 ) =="\"" ) {
+ arg = arg.mid( 1, arg.length() - 2 );
+ }
+ *mProc << arg.utf8();
+ }
+
+ connect( mProc, SIGNAL( receivedStdout( KProcess *, char *, int ) ),
+ SLOT( searchStdout( KProcess *, char *, int ) ) );
+ connect( mProc, SIGNAL( receivedStderr( KProcess *, char *, int ) ),
+ SLOT( searchStderr( KProcess *, char *, int ) ) );
+ connect( mProc, SIGNAL( processExited( KProcess * ) ),
+ SLOT( searchExited( KProcess * ) ) );
+
+ mSearchRunning = true;
+ mSearchResult = "";
+ mStderr = "<b>" + commonSearchProgram + "</b>\n\n";
+
+ mProc->start(KProcess::NotifyOnExit, KProcess::All);
+
+ while (mSearchRunning && mProc->isRunning())
+ kapp->processEvents();
+
+ if ( !mProc->normalExit() || mProc->exitStatus() != 0 ) {
+ kdError() << "Unable to run search program '" << commonSearchProgram
+ << "'" << endl;
+ delete mProc;
+
+ return false;
+ }
+
+ delete mProc;
+
+ // modify the search result
+ mSearchResult = mSearchResult.replace("http://localhost/", "file:/");
+ mSearchResult = mSearchResult.mid( mSearchResult.find( '<' ) );
+
+ mView->beginSearchResult();
+ mView->writeSearchResult( mSearchResult );
+ mView->endSearchResult();
+
+ emit searchFinished();
+ }
+
+ return true;
+}
+
+QString SearchEngine::substituteSearchQuery( const QString &query )
+{
+ QString result = query;
+ result.replace( "%k", mWords );
+ result.replace( "%n", QString::number( mMatches ) );
+ result.replace( "%m", mMethod );
+ result.replace( "%l", mLang );
+ result.replace( "%s", mScope );
+
+ return result;
+}
+
+QString SearchEngine::substituteSearchQuery( const QString &query,
+ const QString &identifier, const QStringList &words, int maxResults,
+ Operation operation, const QString &lang )
+{
+ QString result = query;
+ result.replace( "%i", identifier );
+ result.replace( "%w", words.join( "+" ) );
+ result.replace( "%m", QString::number( maxResults ) );
+ QString o;
+ if ( operation == Or ) o = "or";
+ else o = "and";
+ result.replace( "%o", o );
+ result.replace( "%d", Prefs::indexDirectory() );
+ result.replace( "%l", lang );
+
+ return result;
+}
+
+Formatter *SearchEngine::formatter() const
+{
+ return mView->formatter();
+}
+
+View *SearchEngine::view() const
+{
+ return mView;
+}
+
+void SearchEngine::finishSearch()
+{
+ delete mRootTraverser;
+ mRootTraverser = 0;
+
+ emit searchFinished();
+}
+
+QString SearchEngine::errorLog() const
+{
+ return mStderr;
+}
+
+void SearchEngine::logError( DocEntry *entry, const QString &msg )
+{
+ mStderr += entry->identifier() + ": " + msg;
+}
+
+bool SearchEngine::isRunning() const
+{
+ return mSearchRunning;
+}
+
+SearchHandler *SearchEngine::handler( const QString &documentType ) const
+{
+ QMap<QString,SearchHandler *>::ConstIterator it;
+ it = mHandlers.find( documentType );
+
+ if ( it == mHandlers.end() ) return 0;
+ else return *it;
+}
+
+QStringList SearchEngine::words() const
+{
+ return mWordList;
+}
+
+int SearchEngine::maxResults() const
+{
+ return mMaxResults;
+}
+
+SearchEngine::Operation SearchEngine::operation() const
+{
+ return mOperation;
+}
+
+bool SearchEngine::canSearch( DocEntry *entry )
+{
+ return entry->docExists() && !entry->documentType().isEmpty() &&
+ handler( entry->documentType() );
+}
+
+bool SearchEngine::needsIndex( DocEntry *entry )
+{
+ if ( !canSearch( entry ) ) return false;
+
+ SearchHandler *h = handler( entry->documentType() );
+ if ( h->indexCommand( entry->identifier() ).isEmpty() ) return false;
+
+ return true;
+}
+
+}
+
+#include "searchengine.moc"
+
+// vim:ts=2:sw=2:et