diff options
Diffstat (limited to 'khelpcenter/searchengine.cpp')
-rw-r--r-- | khelpcenter/searchengine.cpp | 502 |
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 |