1
2#include "searchengine.h"
3
4#include "stdlib.h"
5
6#include <QTextDocument>
7#include <KApplication>
8#include <KConfig>
9#include <KDebug>
10#include <KStandardDirs>
11#include <KProcess>
12#include <KShell>
13#include <KLocale>
14#include <KMessageBox>
15
16#include "docmetainfo.h"
17#include "formatter.h"
18#include "view.h"
19#include "searchhandler.h"
20#include "prefs.h"
21
22namespace KHC
23{
24
25SearchTraverser::SearchTraverser( SearchEngine *engine, int level ) :
26 mMaxLevel( 999 ), mEngine( engine), mLevel( level )
27{
28#if 0
29 kDebug() << "SearchTraverser(): " << mLevel
30 << " 0x" << QString::number( int( this ), 16 ) << endl;
31#endif
32}
33
34SearchTraverser::~SearchTraverser()
35{
36#if 0
37 kDebug() << "~SearchTraverser(): " << mLevel
38 << " 0x" << QString::number( int( this ), 16 ) << endl;
39#endif
40
41 QString section;
42 if ( parentEntry() ) {
43 section = parentEntry()->name();
44 } else {
45 section = ("Unknown Section");
46 }
47
48 if ( !mResult.isEmpty() ) {
49 mEngine->view()->writeSearchResult(
50 mEngine->formatter()->sectionHeader( section ) );
51 mEngine->view()->writeSearchResult( mResult );
52 }
53}
54
55void SearchTraverser::process( DocEntry * )
56{
57 kDebug() << "SearchTraverser::process()";
58}
59
60void SearchTraverser::startProcess( DocEntry *entry )
61{
62// kDebug() << "SearchTraverser::startProcess(): " << entry->name() << " "
63// << "SEARCH: '" << entry->search() << "'" << endl;
64
65 if ( !mEngine->canSearch( entry ) || !entry->searchEnabled() ) {
66 mNotifyee->endProcess( entry, this );
67 return;
68 }
69
70// kDebug() << "SearchTraverser::startProcess(): " << entry->identifier()
71// << endl;
72
73 SearchHandler *handler = mEngine->handler( entry->documentType() );
74
75 if ( !handler ) {
76 QString txt;
77 if ( entry->documentType().isEmpty() ) {
78 txt = i18n("Error: No document type specified.");
79 } else {
80 txt = i18n("Error: No search handler for document type '%1'.",
81 entry->documentType() );
82 }
83 showSearchError( handler, entry, txt );
84 return;
85 }
86
87 connectHandler( handler );
88
89 handler->search( entry, mEngine->words(), mEngine->maxResults(),
90 mEngine->operation() );
91
92// kDebug() << "SearchTraverser::startProcess() done: " << entry->name();
93}
94
95void SearchTraverser::connectHandler( SearchHandler *handler )
96{
97 QMap<SearchHandler *,int>::Iterator it;
98 it = mConnectCount.find( handler );
99 int count = 0;
100 if ( it != mConnectCount.end() ) count = *it;
101 if ( count == 0 ) {
102 connect( handler, SIGNAL( searchError( SearchHandler *, DocEntry *, const QString & ) ),
103 SLOT( showSearchError( SearchHandler *, DocEntry *, const QString & ) ) );
104 connect( handler, SIGNAL( searchFinished( SearchHandler *, DocEntry *, const QString & ) ),
105 SLOT( showSearchResult( SearchHandler *, DocEntry *, const QString & ) ) );
106 }
107 mConnectCount[ handler ] = ++count;
108}
109
110void SearchTraverser::disconnectHandler( SearchHandler *handler )
111{
112 QMap<SearchHandler *,int>::Iterator it;
113 it = mConnectCount.find( handler );
114 if ( it == mConnectCount.end() ) {
115 kError() << "SearchTraverser::disconnectHandler() handler not connected."
116 << endl;
117 } else {
118 int count = *it;
119 --count;
120 if ( count == 0 ) {
121 disconnect( handler, SIGNAL( searchError( SearchHandler *, DocEntry *, const QString & ) ),
122 this, SLOT( showSearchError( SearchHandler *, DocEntry *, const QString & ) ) );
123 disconnect( handler, SIGNAL( searchFinished( SearchHandler *, DocEntry *, const QString & ) ),
124 this, SLOT( showSearchResult( SearchHandler *, DocEntry *, const QString & ) ) );
125 }
126 mConnectCount[ handler ] = count;
127 }
128}
129
130DocEntryTraverser *SearchTraverser::createChild( DocEntry *parentEntry )
131{
132// kDebug() << "SearchTraverser::createChild() level " << mLevel;
133
134 if ( mLevel >= mMaxLevel ) {
135 ++mLevel;
136 return this;
137 } else {
138 DocEntryTraverser *t = new SearchTraverser( mEngine, mLevel + 1 );
139 t->setParentEntry( parentEntry );
140 return t;
141 }
142}
143
144DocEntryTraverser *SearchTraverser::parentTraverser()
145{
146// kDebug() << "SearchTraverser::parentTraverser(): level: " << mLevel;
147
148 if ( mLevel > mMaxLevel ) {
149 return this;
150 } else {
151 return mParent;
152 }
153}
154
155void SearchTraverser::deleteTraverser()
156{
157// kDebug() << "SearchTraverser::deleteTraverser()";
158
159 if ( mLevel > mMaxLevel ) {
160 --mLevel;
161 } else {
162 delete this;
163 }
164}
165
166void SearchTraverser::showSearchError( SearchHandler *handler, DocEntry *entry, const QString &error )
167{
168// kDebug() << "SearchTraverser::showSearchError(): " << entry->name()
169// << endl;
170
171 mResult += mEngine->formatter()->docTitle( entry->name() );
172 mResult += mEngine->formatter()->paragraph( error );
173
174 mEngine->logError( entry, error );
175
176 disconnectHandler( handler );
177
178 mNotifyee->endProcess( entry, this );
179}
180
181void SearchTraverser::showSearchResult( SearchHandler *handler, DocEntry *entry, const QString &result )
182{
183// kDebug() << "SearchTraverser::showSearchResult(): " << entry->name()
184// << endl;
185
186 mResult += mEngine->formatter()->docTitle( entry->name() );
187 mResult += mEngine->formatter()->processResult( result );
188
189 disconnectHandler( handler );
190
191 mNotifyee->endProcess( entry, this );
192}
193
194void SearchTraverser::finishTraversal()
195{
196// kDebug() << "SearchTraverser::finishTraversal()";
197
198 mEngine->view()->writeSearchResult( mEngine->formatter()->footer() );
199 mEngine->view()->endSearchResult();
200
201 mEngine->finishSearch();
202}
203
204
205SearchEngine::SearchEngine( View *destination )
206 : QObject(),
207 mProc( 0 ), mSearchRunning( false ), mView( destination ),
208 mRootTraverser( 0 )
209{
210 mLang = KGlobal::locale()->language().left( 2 );
211}
212
213SearchEngine::~SearchEngine()
214{
215 delete mRootTraverser;
216}
217
218bool SearchEngine::initSearchHandlers()
219{
220 const QStringList resources = KGlobal::dirs()->findAllResources(
221 "appdata", "searchhandlers/*.desktop" );
222 QStringList::ConstIterator it;
223 for( it = resources.constBegin(); it != resources.constEnd(); ++it ) {
224 QString filename = *it;
225 kDebug() << "SearchEngine::initSearchHandlers(): " << filename;
226 SearchHandler *handler = SearchHandler::initFromFile( filename );
227 if ( !handler ) {
228 QString txt = i18n("Unable to initialize SearchHandler from file '%1'.",
229 filename );
230 kWarning() << txt ;
231// KMessageBox::sorry( mView->widget(), txt );
232 } else {
233 QStringList documentTypes = handler->documentTypes();
234 QStringList::ConstIterator it;
235 for( it = documentTypes.constBegin(); it != documentTypes.constEnd(); ++it ) {
236 mHandlers.insert( *it, handler );
237 }
238 }
239 }
240
241 if ( mHandlers.isEmpty() ) {
242 QString txt = i18n("No valid search handler found.");
243 kWarning() << txt ;
244// KMessageBox::sorry( mView->widget(), txt );
245 return false;
246 }
247
248 return true;
249}
250
251void SearchEngine::searchExited(int exitCode, QProcess::ExitStatus exitStatus)
252{
253 Q_UNUSED(exitCode);
254 Q_UNUSED(exitStatus);
255 kDebug() << "Search terminated";
256 mSearchRunning = false;
257}
258
259bool SearchEngine::search( const QString & words, const QString & method, int matches,
260 const QString & scope )
261{
262 if ( mSearchRunning ) return false;
263
264 // These should be removed
265 mWords = words;
266 mMethod = method;
267 mMatches = matches;
268 mScope = scope;
269
270 // Saner variables to store search parameters:
271 mWordList = words.split(' ');
272 mMaxResults = matches;
273 if ( method == "or" ) mOperation = Or;
274 else mOperation = And;
275
276 KConfigGroup cfg(KGlobal::config(), "Search");
277 QString commonSearchProgram = cfg.readPathEntry( "CommonProgram", QString() );
278 bool useCommon = cfg.readEntry( "UseCommonProgram", false);
279
280 if ( commonSearchProgram.isEmpty() || !useCommon ) {
281 if ( !mView ) {
282 return false;
283 }
284
285 QString txt = i18n("Search Results for '%1':", Qt::escape(words) );
286
287 mStderr = "<b>" + txt + "</b>\n";
288
289 mView->beginSearchResult();
290 mView->writeSearchResult( formatter()->header( i18n("Search Results") ) );
291 mView->writeSearchResult( formatter()->title( txt ) );
292
293 if ( mRootTraverser ) {
294 kDebug() << "SearchEngine::search(): mRootTraverser not null.";
295 return false;
296 }
297 mRootTraverser = new SearchTraverser( this, 0 );
298 DocMetaInfo::self()->startTraverseEntries( mRootTraverser );
299
300 return true;
301 } else {
302 QString lang = KGlobal::locale()->language().left(2);
303
304 if ( lang.toLower() == "c" || lang.toLower() == "posix" )
305 lang = "en";
306
307 // if the string contains '&' replace with a '+' and set search method to and
308 if (mWords.indexOf("&") != -1) {
309 mWords.replace('&', ' ');
310 mMethod = "and";
311 }
312
313 // replace whitespace with a '+'
314 mWords = mWords.trimmed();
315 mWords = mWords.simplified();
316 mWords.replace(QRegExp("\\s"), "+");
317
318 commonSearchProgram = substituteSearchQuery( commonSearchProgram );
319
320 kDebug() << "Common Search: " << commonSearchProgram;
321
322 mProc = new KProcess();
323 *mProc << KShell::splitArgs(commonSearchProgram);
324
325 connect( mProc, SIGNAL( finished(int, QProcess::ExitStatus) ),
326 this, SLOT( searchExited(int, QProcess::ExitStatus) ) );
327
328 mSearchRunning = true;
329 mSearchResult = "";
330 mStderr = "<b>" + commonSearchProgram + "</b>\n\n";
331
332 mProc->start();
333 if (!mProc->waitForStarted()) {
334 kError() << "could not start search program '" << commonSearchProgram
335 << "'" << endl;
336 delete mProc;
337 return false;
338 }
339
340 while (mSearchRunning && mProc->state() == QProcess::Running)
341 kapp->processEvents();
342
343 // no need to use signals/slots
344 mStderr += mProc->readAllStandardError();
345 mSearchResult += mProc->readAllStandardOutput();
346
347 if ( mProc->exitStatus() == KProcess::CrashExit || mProc->exitCode() != 0 ) {
348 kError() << "Unable to run search program '" << commonSearchProgram
349 << "'" << endl;
350 delete mProc;
351
352 return false;
353 }
354
355 delete mProc;
356
357 // modify the search result
358 mSearchResult = mSearchResult.replace("http://localhost/", "file:/");
359 mSearchResult = mSearchResult.mid( mSearchResult.indexOf( '<' ) );
360
361 mView->beginSearchResult();
362 mView->writeSearchResult( mSearchResult );
363 mView->endSearchResult();
364
365 emit searchFinished();
366 }
367
368 return true;
369}
370
371QString SearchEngine::substituteSearchQuery( const QString &query )
372{
373 QString result = query;
374 result.replace( QLatin1String("%k"), mWords );
375 result.replace( QLatin1String("%n"), QString::number( mMatches ) );
376 result.replace( QLatin1String("%m"), mMethod );
377 result.replace( QLatin1String("%l"), mLang );
378 result.replace( QLatin1String("%s"), mScope );
379
380 return result;
381}
382
383QString SearchEngine::substituteSearchQuery( const QString &query,
384 const QString &identifier, const QStringList &words, int maxResults,
385 Operation operation, const QString &lang, const QString& binary )
386{
387 QString result = query;
388 result.replace( QLatin1String("%i"), identifier );
389 result.replace( QLatin1String("%w"), words.join( "+" ) );
390 result.replace( QLatin1String("%m"), QString::number( maxResults ) );
391 QString o = QLatin1String(operation == Or ? "or" : "and");
392 result.replace( QLatin1String("%o"), o );
393 result.replace( QLatin1String("%d"), Prefs::indexDirectory() );
394 result.replace( QLatin1String("%l"), lang );
395 result.replace( QLatin1String("%b"), binary );
396
397 return result;
398}
399
400Formatter *SearchEngine::formatter() const
401{
402 return mView->formatter();
403}
404
405View *SearchEngine::view() const
406{
407 return mView;
408}
409
410void SearchEngine::finishSearch()
411{
412 delete mRootTraverser;
413 mRootTraverser = 0;
414
415 emit searchFinished();
416}
417
418QString SearchEngine::errorLog() const
419{
420 return mStderr;
421}
422
423void SearchEngine::logError( DocEntry *entry, const QString &msg )
424{
425 mStderr += entry->identifier() + QLatin1String(": ") + msg;
426}
427
428bool SearchEngine::isRunning() const
429{
430 return mSearchRunning;
431}
432
433SearchHandler *SearchEngine::handler( const QString &documentType ) const
434{
435 return mHandlers.value( documentType, 0 );
436}
437
438QStringList SearchEngine::words() const
439{
440 return mWordList;
441}
442
443int SearchEngine::maxResults() const
444{
445 return mMaxResults;
446}
447
448SearchEngine::Operation SearchEngine::operation() const
449{
450 return mOperation;
451}
452
453bool SearchEngine::canSearch( DocEntry *entry )
454{
455 return entry->docExists() && !entry->documentType().isEmpty() &&
456 handler( entry->documentType() );
457}
458
459bool SearchEngine::needsIndex( DocEntry *entry )
460{
461 if ( !canSearch( entry ) ) return false;
462
463 SearchHandler *h = handler( entry->documentType() );
464 if ( !h || h->indexCommand( entry->identifier() ).isEmpty() ) return false;
465
466 return true;
467}
468
469}
470
471#include "searchengine.moc"
472
473// vim:ts=2:sw=2:et
474